<?php


namespace App\CustHub\Helpers;

class WxDownload
{

    private $bufferSize= 1024 * 64;
    protected function wxDownloadRequest(string $url, string $authKey, int $seq, int $rangeStart, int $rangeEnd)
    {
        $headers = [
            'Connection: keep-alive',
            "authkey: {$authKey}",
            'Content-Type: application/octet-stream',
            'User-Agent: MicroMessenger Client',
            'C-Seq: ' . $seq,
            'Host: imunion.weixin.qq.com',
            "Range: bytes={$rangeStart}-{$rangeEnd}",
        ];

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL            => $url,
            CURLOPT_CUSTOMREQUEST  => 'POST',
            CURLOPT_HTTPHEADER     => $headers,
            CURLOPT_SSL_VERIFYPEER => false,  // 跳过 TLS 校验
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_RETURNTRANSFER => true,   // 返回内容作为 string
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT        => 60,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err      = curl_error($ch);
        curl_close($ch);
        logger()->info("流片段下载.info, http, httpCode".$httpCode.", err=".$err);
        return ($httpCode == 200 || $httpCode == 206) ? $response : false;
    }

    protected function pkcs7Unpad(string $data): string
    {
        $len = strlen($data);
        if ($len === 0) {
            throw new \RuntimeException('pkcs7UnpadErr.1');
        }
        $pad = ord($data[$len - 1]);
        if ($pad < 1 || $pad > 16 || $pad > $len) {
            throw new \RuntimeException("pkcs7UnpadErr.2: {$pad}");
        }
        return substr($data, 0, $len - $pad);
    }

    protected function aesEcbDecrypt(string $data, string $key, bool $unpad = false): string
    {
        $decrypted = openssl_decrypt(
            $data,
            sprintf('AES-%d-ECB', strlen($key) * 8),
            $key,
            OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
        );
        if ($decrypted === false) {
            throw new \RuntimeException('aesEcbDecryptErr.1');
        }
        return $unpad ? $this->pkcs7Unpad($decrypted) : $decrypted;
    }

    public function wxDownload(string $url, string $aesKey, string $authKey, int $fileSize, string $dstPath)
    {
        $bufferSize = $this->bufferSize;
        $leftSize  = $fileSize + (0x10 - $fileSize % 0x10); // 保证下载为aeskey长度倍数，防止最近一个流片解密失败
        $aesKeyBin = hex2bin($aesKey);
        if ($aesKeyBin === false) {
            throw new \RuntimeException(sprintf('aesKey错误: %s', $aesKey));
        }
        $fp = fopen($dstPath, 'wb');
        if ($fp === false) {
            throw new \RuntimeException(sprintf('无法打开文件: %s', $dstPath));
        }

        $downloaded = 0;
        $seq        = 1;
        while ($downloaded < $leftSize) {
            // 流片段偏移计算
            $start  = $downloaded;
            $end    = min($downloaded + $bufferSize - 1, $leftSize - 1);
            $isLast = $end === ($leftSize - 1);

            // 流片段下载
            $partDataBin = $this-> wxDownloadRequest($url, $authKey, $seq, $start, $end);
            if ($partDataBin === false) {
                throw new \RuntimeException(sprintf('下载流切片失败: %s', $dstPath));
            }
            logger()->info("流片段下载.info, ReqRange=$start-$end, ResLen=".strlen($partDataBin)."\n");
            // 流片段解密
            $partDataBinDecrypted = $this->aesEcbDecrypt($partDataBin, $aesKeyBin, $isLast);

            // 流片段写入文件
            $written = fwrite($fp, $partDataBinDecrypted);
            if ($written === false) {
                fclose($fp);
                throw new \RuntimeException(sprintf('流字节写入失败: %s', $dstPath));
            }
            $downloaded += strlen($partDataBin);
            ++$seq;
        }
    }
}